
package com.ifcar.app.view;

import com.ifcar.app.util.VersionCompat;
import com.ifcar.app.activity.R;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import android.widget.Scroller;
import android.widget.ViewSwitcher;

public abstract class ImageViewer extends ViewSwitcher {

    public static final int INVALID_VALUE = -1;

    private static final int NONE = 0;

    private static final int DOWN = NONE + 1;

    private static final int DRAG = DOWN + 1;

    private ArrayList<Object> mArrayList = new ArrayList<Object>();

    private int mCurrent = INVALID_VALUE;

    private int mNext = INVALID_VALUE;

    private FrameLayout mPreAppendView;

    private FrameLayout mNextAppendView;
    
    private int mActivePointerId = INVALID_VALUE;

    private int mTouchState = NONE;

    private float mLastX;

    private float mLastY;

    private int mTouchSlop;
    
    private float mDensity;

    private boolean mVertical;

    private Scroller mScroller;

    private Rect mRect = new Rect();

    private AppendViewChangeListener mAppendViewChangeListener;

    private HashMap<Object, WeakReference<Bitmap>> mBitmapCache = new HashMap<Object, WeakReference<Bitmap>>();
    
    private OnItemListener mOnItemClickListener;

    public ImageViewer(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ImageViewer(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        mDensity = getResources().getDisplayMetrics().density;
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
        mScroller = new Scroller(context);
        mPreAppendView = new FrameLayout(context);
        LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
        mPreAppendView.setLayoutParams(lp);
        mNextAppendView = new FrameLayout(context);
        lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
        mNextAppendView.setLayoutParams(lp);

        hideAppendView();

    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        getCurrentView().setVisibility(View.VISIBLE);
        getNextView().setVisibility(View.INVISIBLE);
        ImageViewTouch child = getImageViewTouch(getCurrentView());
        child.setImageViewer(this);

        child = getImageViewTouch(getNextView());
        child.setImageViewer(this);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        
        if (changed) {
            getHitRect(mRect);
            layoutAppend(mPreAppendView, changed, left, top, right, bottom);
            layoutAppend(mNextAppendView, changed, left, top, right, bottom);
        }
    }

    private void layoutAppend(View appendView, boolean changed, int left, int top, int right,
            int bottom) {
        appendView.layout(left, -appendView.getMeasuredHeight(), appendView.getMeasuredWidth(), 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureAppend(mPreAppendView, widthMeasureSpec, heightMeasureSpec);
        measureAppend(mNextAppendView, widthMeasureSpec, heightMeasureSpec);
    }

    private void measureAppend(View appendView, int widthMeasureSpec, int heightMeasureSpec) {
        measureChildWithMargins(appendView, widthMeasureSpec, 0, heightMeasureSpec, 0);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);

        final boolean isPreShow = isPreShow();
        final boolean isNextShow = isNextShow();
        final int appendSize = getAppendSize();

        final View appendView = getAppendView();
        // TODO 暂时在横向拖动时不画appendview
        if (mVertical && appendView != null && appendView.getVisibility() == VISIBLE
                && appendSize > 0 && (isPreShow || isNextShow)) {
            if (isPreShow) {
                drawOffsetChild(canvas, appendView, getDrawingTime(), getRectStart() - appendSize);
            } else if (isNextShow) {
                drawOffsetChild(canvas, appendView, getDrawingTime(), getRectEnd());
            }
        }

        final View nextView = getNextView();

        final boolean isNextValid = isNextValid();
        if (isPreShow && isNextValid) {
            drawOffsetChild(canvas, nextView, getDrawingTime(),
                    (getRectStart() - appendSize - getRectSize()));
        } else if (isNextShow && isNextValid) {
            drawOffsetChild(canvas, nextView, getDrawingTime(), (getRectEnd() + appendSize));
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (getCurrentView() == child) {
            return drawOffsetChild(canvas, child, drawingTime, getRectStart());
        }
        return super.drawChild(canvas, child, drawingTime);
    }

    private boolean drawOffsetChild(Canvas canvas, View childView, long drawingTime, int newStart) {
        int offset = newStart - (mVertical ? childView.getTop() : childView.getLeft());
//        if (getNextView() == childView) {
//            View loadingBar = childView.findViewById(R.id.loadingbar);
//            System.out.println("draw next " + loadingBar.getVisibility() + ","+loadingBar.getWidth()+","+loadingBar.getLeft());
//        }
        if (mVertical) {
            childView.offsetTopAndBottom(offset);
        } else {
            childView.offsetLeftAndRight(offset);
        }

        // if (getNextView() == childView) {
        // logViewRect(childView);
        // }
        boolean ret = super.drawChild(canvas, childView, drawingTime);

        if (mVertical) {
            childView.offsetTopAndBottom(-offset);
        } else {
            childView.offsetLeftAndRight(-offset);
        }
        return ret;
    }

    private void logViewRect(View view) {
        final Rect r = new Rect();
        view.getHitRect(r);
//        System.out.println("1:" + r);
//        System.out.println("2:" + mRect);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return onTouch(event);        
    }
    
    
    public boolean onTouch(MotionEvent event) {
        if (mCurrent == INVALID_VALUE || !mScroller.isFinished()) {
            mTouchState = DRAG;
            mLastX = event.getX();
            mLastY = event.getY();
            return true;
        }
        
        boolean ret = false;
        final int action = event.getAction();
        switch (action & MotionEventCompat.getActionMasked(event)) {
            case MotionEvent.ACTION_DOWN:
                mTouchState = DOWN;
                mLastX = event.getX();
                mLastY = event.getY();
                if (isNextShow() || isPreShow()) {
                    ret = true;
                } else {
                    ImageViewTouch imageViewTouch = getImageViewTouch(getCurrentView());
                    if (imageViewTouch == null || !imageViewTouch.isReady()) {
                        ret = true;
                    }
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(event);
                if (VersionCompat.getMotionEventCompat().getPointerCount(event) == 2) {
                    mTouchState = DOWN;
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (VersionCompat.getMotionEventCompat().getPointerCount(event) == 2) {
                    mActivePointerId = MotionEventCompat.getPointerId(event, 0);
                    if (mTouchState == DOWN) {
                        hideAppendView();
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mTouchState == DOWN) {
                    final float x = event.getX();
                    final float y = event.getY();
                    final float deltaX = x - mLastX;
                    final float deltaY = y - mLastY;

                    if (Math.abs(deltaX) > mTouchSlop || Math.abs(deltaY) > mTouchSlop) {
                        mTouchState = DRAG;
                    }
                }

                if (mTouchState == DRAG) {
                    float delta = mVertical ? (event.getY() - mLastY) : (event.getX() - mLastX);

                    onMove((int) delta);
                    ret = true;
                }
                mLastX = event.getX();
                mLastY = event.getY();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mTouchState == DRAG) {
                    onUp();
                }
                mActivePointerId = INVALID_VALUE;
                mTouchState = NONE;
                break;
            default:
                break;
        }

        return ret;
    }
    
    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            // TODO: Make this decision more intelligent.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastX = MotionEventCompat.getX(ev, newPointerIndex);
            mLastY =  MotionEventCompat.getY(ev, newPointerIndex);
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }
    
    public boolean hasInterpreterTouch() {
        return isNextShow() || isPreShow();
    }

    @Override
    public void showNext() {
        if (mNext != INVALID_VALUE) {
            setCurrentInternal(mNext);
            super.showNext();
            getHitRect(mRect);
            getNextView().setVisibility(View.INVISIBLE);
        }
    }

    private int getRectSize() {
        return mVertical ? mRect.height() : mRect.width();
    }

    private int getRectStart() {
        return mVertical ? mRect.top : mRect.left;
    }

    private int getRectEnd() {
        return mVertical ? mRect.bottom : mRect.right;
    }

    private boolean isPreShow() {
        return getRectStart() > 0;
    }

    private boolean isNextShow() {
        return getRectStart() < 0;
    }

    private boolean isNextValid() {
        return mNext != INVALID_VALUE;
    }

    private boolean willToNext() {
        int size = getAppendSize() / 2;
        if (size <= 0) {
            size = (int) (30 * mDensity);
        }
//        System.out.println(getRectStart()+","+size);
        return Math.abs(getRectStart()) > size;
    }

    private int getAppendSize() {
        final View appendView = getAppendView();
        return appendView == null ? 0 : appendView.getHeight();
    }

    private FrameLayout getAppendView() {
        if (isPreShow()) {
            return mPreAppendView;
        } else if (isNextShow()) {
            return mNextAppendView;
        }
        return null;
    }

    private void onMove(int delta) {
        if (delta == 0) {
            return;
        }
        final boolean isNextShow = isNextShow();
        final boolean isPreShow = isPreShow();

        int offset = 0;
        if (!isNextShow && (delta > 0 || (isPreShow && delta < 0))) {
            if (delta < 0) {
                delta = Math.max(-getRectStart(), delta);
            } else {
                delta = (int) (delta * (((float) (getRectSize() - getRectStart())) / getRectSize() / 2));
            }
            offset = delta;
        } else if (!isPreShow && (delta < 0 || (isNextShow && delta > 0))) {
            if (delta > 0) {
                delta = Math.min(getRectSize() - getRectEnd(), delta);
            } else {
                delta = (int) (delta * (((float) getRectEnd()) / getRectSize() / 2));
            }
            offset = delta;
        }

        offsetView(offset);

    }

    private void onUp() {
        if (isPreShow()) {
            if (isNextValid() && willToNext()) {
                final int start = getRectStart();
                final int dy = getRectSize() + getAppendSize() - start;
                mScroller.startScroll(0, start, 0, dy, 500);
                post(new Runnable() {
                    @Override
                    public void run() {
                        if (mScroller.computeScrollOffset()) {
                            final int trans = mScroller.getCurrY() - getRectStart();
                            offsetView(trans);
                            if (getRectStart() != mScroller.getFinalY()) {
                                post(this);
                            } else {
                                showNext();
                            }
                        }
                    }
                });
                return;
            }
            final int start = getRectStart();
            final float incrementPerMs = start / 200f;
            final long startTime = System.currentTimeMillis();
            post(new Runnable() {
                @Override
                public void run() {
                    long now = System.currentTimeMillis();
                    float currentMs = Math.min(200f, now - startTime);
                    float target = start - (incrementPerMs * currentMs) - getRectStart();
                    offsetView((int) target);
                    if (currentMs < 200f) {
                        post(this);
                    }
                }
            });
        } else if (isNextShow()) {
            if (isNextValid() && willToNext()) {
                final int start = getRectStart();
                final int dy = -(getRectSize() + getAppendSize()) - start;
                mScroller.startScroll(0, start, 0, dy, 500);
                post(new Runnable() {
                    @Override
                    public void run() {
                        if (mScroller.computeScrollOffset()) {
                            final int trans = mScroller.getCurrY() - getRectStart();
                            offsetView(trans);
                            if (getRectStart() != mScroller.getFinalY()) {
                                post(this);
                            } else {
                                showNext();
                            }
                        }
                    }
                });
                return;
            }
            final int bottom = getRectEnd();
            final float incrementPerMs = (getRectSize() - bottom) / 200f;
            final long startTime = System.currentTimeMillis();
            post(new Runnable() {
                @Override
                public void run() {
                    long now = System.currentTimeMillis();
                    float currentMs = Math.min(200f, now - startTime);
                    float target = bottom + (incrementPerMs * currentMs) - getRectEnd();

                    offsetView((int) target);
                    if (currentMs < 200f) {
                        post(this);
                    }
                }
            });
        }

    }

    private void offsetView(int offset) {
        if (offset == 0) {
            return;
        }
        // final int lastLeft = mRect.left;
        final boolean lastIsPreShow = isPreShow();
        final boolean lastIsNextShow = isNextShow();
        final boolean lastWillToNext = willToNext();

        if (mVertical) {
            mRect.top += offset;
            mRect.bottom += offset;
        } else {
            mRect.left += offset;
            mRect.right += offset;
        }

        final boolean isPreShow = isPreShow();
        final boolean isNextShow = isNextShow();
        final boolean willToNext = willToNext();

        int status = INVALID_VALUE;
        if (isPreShow) {
            if (!lastIsPreShow) {
                status = prepareNext() ? AppendViewChangeListener.STATUS_SHOW_PRE
                        : AppendViewChangeListener.STATUS_INVALID_PRE;
            } else if (!lastWillToNext && willToNext && isNextValid()) {
                status = AppendViewChangeListener.STATUS_WILL_PRE;
            } else if (!willToNext && lastWillToNext && isNextValid()) {
                status = AppendViewChangeListener.STATUS_SHOW_PRE;
            }
        } else if (isNextShow) {
            if (!lastIsNextShow) {
                status = prepareNext() ? AppendViewChangeListener.STATUS_SHOW_NEXT
                        : AppendViewChangeListener.STATUS_INVALID_NEXT;
            } else if (!lastWillToNext && willToNext && isNextValid()) {
                status = AppendViewChangeListener.STATUS_WILL_NEXT;
            } else if (!willToNext && lastWillToNext && isNextValid()) {
                status = AppendViewChangeListener.STATUS_SHOW_NEXT;
            }
        }

        if (status != INVALID_VALUE) {
            callChangeAppend(status);
        }

        invalidate();
    }

    private boolean prepareNext() {
        boolean isPreShow = isPreShow();
        boolean isNextShow = isNextShow();

        if (isPreShow) {
            showAppendView();
        } else if (isNextShow) {
            showAppendView();
        }

        mNext = isPreShow ? mCurrent - 1 : mCurrent + 1;
        if (mNext < 0 || mNext >= getCount()) {
            mNext = INVALID_VALUE;
        }
        final boolean isNextValid = isNextValid();

        if (isNextValid) {
            final ImageViewTouch nextView = getImageViewTouch(getNextView());
            if (nextView != null) {
                setViewBitmap(nextView, getBitmapByIndex(mNext));
                if (isPreShow) {
                    RectF r = nextView.getMapRect();
                    nextView.postTranslateCenter(0, getRectSize() - r.height());
                }
            }            
        }
        return isNextValid;
    }

    private void callChangeAppend(int status) {
        FrameLayout appendView = getAppendView();
        if (appendView == null) {
            return;
        }
        View view = appendView.getChildAt(0);

        if (view != null) {
            switch (status) {
                case AppendViewChangeListener.STATUS_SHOW_PRE:
                    break;
                case AppendViewChangeListener.STATUS_SHOW_NEXT:
                    break;
                case AppendViewChangeListener.STATUS_WILL_PRE:
                    break;
                case AppendViewChangeListener.STATUS_WILL_NEXT:
                    break;
                case AppendViewChangeListener.STATUS_INVALID_PRE:
                    break;
                case AppendViewChangeListener.STATUS_INVALID_NEXT:
                    break;

                default:
                    break;
            }
        }
        if (mAppendViewChangeListener != null) {
            mAppendViewChangeListener.onChange(status, view);
        }
    }

    private void showAppendView() {
        View appendView = getAppendView();
        if (appendView != null && appendView.getVisibility() != VISIBLE) {
            appendView.setVisibility(VISIBLE);
            invalidate();
        }
    }

    private void hideAppendView() {
        View appendView = getAppendView();
        if (appendView != null && appendView.getVisibility() == VISIBLE) {
            appendView.setVisibility(GONE);
            invalidate();
        }
    }

    private Bitmap getCache(Object key) {
        WeakReference<Bitmap> r = mBitmapCache.get(key);
        return r == null ? null : r.get();
    }

    private void addCache(Object key, Bitmap bitmap) {
        mBitmapCache.put(key, new WeakReference<Bitmap>(bitmap));
    }

    private void removeCache(Object key) {
        mBitmapCache.remove(key);
    }

    private void clearCache() {
        mBitmapCache.clear();
    }

    private Bitmap getBitmapInternal(int position, Object path) {
        Bitmap bitmap = getCache(path);
        
        if (isCacheChanged(path, bitmap)) {
            bitmap = getBitmap(position, path, bitmap);
            addCache(path, bitmap);
        }
        return bitmap;
    }

    private ImageViewTouch getImageViewTouch(View view) {
        if (view == null) {
            return null;
        }
        return (ImageViewTouch)view.findViewById(R.id.imageviewtouch);
    }
    
    protected void setViewBitmap(ImageViewTouch view, Bitmap bitmap) {
        if (view == null) {
            return;
        }
        view.setImageBitmapResetBase(bitmap, true);
    }

    private void setCurrentInternal(int current) {
        if (mCurrent == current) {
            return;
        }
        if (current < 0 || current >= getCount()) {
            mCurrent = INVALID_VALUE;
        } else {
            mCurrent = current;
        }
        mScroller.forceFinished(true);
        mNext = INVALID_VALUE;
        performItemSelected();
    }

    private Bitmap getBitmapByIndex(int i) {
        if (i < 0 || i >= mArrayList.size()) {
            return null;
        }
        return getBitmapInternal(i, mArrayList.get(i));
    }

    /**
     * 
     * @param current true说明是当前view需要取图片
     * @param key
     * @return
     */
    protected abstract Bitmap getBitmap(int position, Object key, Bitmap cached);
    
    /**
     * 说明缓存图片有变化
     * @param current true说明是当前view需要取图片
     * @param key
     * @return
     */
    protected boolean isCacheChanged(Object key, Bitmap cached) {
        return cached == null || cached.isRecycled();
    }

    public void setList(List list, int position) {
        clearCache();
        mArrayList.clear();
        mArrayList.addAll(list);
        mCurrent = INVALID_VALUE;
        setCurrent(position);
        
    }

    public int getCount() {
        if (mArrayList == null) {
            return 0;
        }
        return mArrayList.size();
    }

    public int getCurrent() {
        return mCurrent;
    }
    
    public int getNext() {
        return mNext;
    }

    public void setCurrent(int current) {
        if (mCurrent == current) {
            return;
        }
        setCurrentInternal(current);
        setViewBitmap(getImageViewTouch(getCurrentView()),
                mCurrent != INVALID_VALUE ? getBitmapByIndex(mCurrent) : null);

    }
    
    public void refreshCurrent() {
        if (mCurrent == INVALID_VALUE) {
            return;
        }
        final int c = mCurrent;
        mCurrent = INVALID_VALUE;
        setCurrent(c);        
    }

    public void setVertical(boolean vertival) {
        if (mVertical != vertival) {
            mVertical = vertival;
            requestLayout();
        }
    }
    
    public boolean isVertical() {
        return mVertical;
    }

    public void setAppendViewChangeListener(AppendViewChangeListener listener) {
        mAppendViewChangeListener = listener;
        mPreAppendView.removeAllViews();
        mNextAppendView.removeAllViews();
        if (listener != null) {
            View view = listener.createView(true);
            if (view != null) {
                mPreAppendView.addView(view);
            }

            view = listener.createView(false);
            if (view != null) {
                mNextAppendView.addView(view);
            }
        }

        requestLayout();
        invalidate();
    }
    
    public void setOnItemListener(OnItemListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }
    
    protected void performItemClick() {
        if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemClick(getCurrent());
        }
    }
    
    protected void performItemSelected() {
        if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemSelected(getCurrent());
        }
    }

    public interface AppendViewChangeListener {
        public static final int STATUS_SHOW_PRE = 0;

        public static final int STATUS_WILL_PRE = STATUS_SHOW_PRE + 1;

        public static final int STATUS_INVALID_PRE = STATUS_WILL_PRE + 1;

        public static final int STATUS_SHOW_NEXT = STATUS_INVALID_PRE + 1;

        public static final int STATUS_WILL_NEXT = STATUS_SHOW_NEXT + 1;

        public static final int STATUS_INVALID_NEXT = STATUS_WILL_NEXT + 1;

        public View createView(boolean pre);

        public void onChange(int status, View v);
    }
    
    public interface OnItemListener {
        public void onItemClick(int position);
        public void onItemSelected(int position);
    }
}
